package com.ch3cooh.common.service;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import org.apache.log4j.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;

/**
 * redis -分布式锁，jdk1.8及以上
 */
public class RedisLock {

    private Logger logger = Logger.getLogger(RedisLock.class);

     StringRedisTemplate redisTemplate;

    /* 将set升级为 setnx*/
    private static final String NX = "NX";

    /* 让上面升级成 setnx 支持 expire , 时间单位为: 秒*/
    private static final String EX = "EX";

    /*  设置成功后的返回的默认字符串提示 */
    private static final String SET_OK = "ok";

    /* 默认请求锁的超时时间*/
    private static final int DEFAULT_TRY_TIMEOUT = 500;

    /* 锁的ttl , 默认60秒*/
    private static final int LOCK_TTL = 60;

    /*  redis执行lua脚本命令是一个原子操作 , 因此释放锁通过lua脚本执行*/
    private static final String UNLOCK_LUA;
    static{
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    /* 锁的key */
    private String lockKey;

    /* 锁的value*/
    private String lockValue;

    //日志 ...

    /* 锁的ttl，单位 s */
    private int lockTTL = LOCK_TTL;

    /*  锁的超时时间,单位 ms */
    private int timeOut = DEFAULT_TRY_TIMEOUT;

    /* 是否被锁 */
    private volatile boolean isLocked = false;

    private final  Random random = new Random();

    /* 防止1个RedisLock对象被设为静态常量被多个线程使用，出现的线程安全问题 */
    private ReentrantLock reentrantLock = new ReentrantLock();

    public RedisLock(StringRedisTemplate redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
    }

    public RedisLock(StringRedisTemplate redisTemplate, String lockKey, int lockTTL,int tomeOut) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.lockTTL = lockTTL;
        this.timeOut = tomeOut;
    }

    /**
     * 获取锁，如果获取失败，会循环尝试获取锁，直到超时
     * @return  true表示获取成功 ； false表示获取失败
     */
    public boolean lock(){
        lockValue = UUID.randomUUID().toString().replace("-","");
        long now = System.currentTimeMillis();
        while(System.currentTimeMillis() - now <= timeOut){
           if (SET_OK.equalsIgnoreCase( this.doSet(lockKey, lockValue) )){
               return true;
           }
            sleep(10, 50000);
        }
        return false;
    }


    /**
     *  尝试获取锁，如果获取失败则立即返回false
     * @return     true表示获取成功 ； false表示获取失败
     */
    public boolean tryLock(){
        lockValue = UUID.randomUUID().toString().replace("-","");
  //      String result = this.set(lockKey,lockValue);
        return SET_OK.equalsIgnoreCase( this.doSet(lockKey, lockValue) );
    }

    /**
     * 一直循环尝试获取锁
     * @return
     */
    public boolean lockWithoutTimeout(){
        lockValue = UUID.randomUUID().toString().replace("-","");
        for(;;){
            if(SET_OK.equalsIgnoreCase( this.doSet(lockKey, lockValue))){
                return true;
            }
            sleep(10, 50000);
        }
    }

    /**
     * 解锁
     * @return
     */
    public boolean unlock(){
        if(isLocked){
            List<String> key = Arrays.asList(new String[]{lockKey});
            List<String> value = Arrays.asList(new String[]{lockValue});
            return redisTemplate.execute((RedisConnection conn)->{
                long result = -1L;
                Object nativeConnection = conn.getNativeConnection();
                try {
                    reentrantLock.lock();
                    if(nativeConnection instanceof Jedis){  //单机的session对象
                        result =  (long)((Jedis)nativeConnection).eval(UNLOCK_LUA,  key, value);
                    }else if(nativeConnection instanceof JedisCluster){    //集群session对象
                        result =  (long)((JedisCluster)nativeConnection).eval(UNLOCK_LUA,  key, value);
                    }
                    isLocked = false;
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    reentrantLock.unlock();
                }
                if( 1!= result ){
                    System.err.println(String.format("分布式锁解锁失败, key=%s, 尝试解锁时间: %d" , lockKey,System.currentTimeMillis()));
                    return false;
                }
                return true;
            });
        }
        return false;
    }


    /**
     *  真正将数据放到redis中
     * @param lockKey
     * @param lockValue
     */
    private String doSet(String lockKey, String lockValue) {
        Assert.isTrue(!StringUtils.isEmpty(lockKey),"lockKey 不能为空");
        return redisTemplate.execute((RedisConnection conn)->{
            String result = "nil";
            Object nativeConnection = conn.getNativeConnection();
            try {
                reentrantLock.lock();
                if(nativeConnection instanceof Jedis){       //单机session对象
                    result =  ((Jedis)nativeConnection).set(lockKey,lockValue,NX,EX,lockTTL);
                }else if(nativeConnection instanceof JedisCluster){     //集群session对象
                    result =  ((JedisCluster)nativeConnection).set(lockKey,lockValue,NX,EX,lockTTL);
                }
                isLocked = SET_OK.equalsIgnoreCase(result)?true:false;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
            return result;
        });
    }

    /**
     * @param millis 毫秒
     * @param nanos  纳秒
     */
    private void sleep(long millis, int nanos) {
        try {
            Thread.sleep(millis, random.nextInt(nanos));
        } catch (InterruptedException e) {
            logger.info("获取分布式锁休眠被中断：", e);
        }
    }

}
